在我的經驗裡看到正規表達式最多的地方是在做表單檢驗的時候,對於那串複雜的符號和奇怪的規則,我總是看過就忘記。事實上不用正規表達式,也是能用其他的方法寫出檢查的規則,只是就要寫大量的程式碼來判斷各種狀況,用正規表達式的話可以很簡潔優雅的處理同樣的事情。
簡單來說,正規表達式是在一個字串裡尋找特定模式的文字,比如說你在文字編輯器裡打開搜尋功能,尋找特定的文字,正規表達式就是這樣,但它的能力可以實現複雜的搜尋規則,也可以替換文字。
建立正規表達式的方法有二個。第一個是用正規表達式實值,要在頭尾加上斜線/
,被二條斜線包起來的這組值是正規表達式,就像我們用引號包住文字代表字串一樣。
例如我們要找到文字當中完全匹配"test"
的字串:
const pattern = /test/;
第二個是使用 RegExp
建構器函式,將規則以字串方式代入參數,這個方法適用在。
const pattern = new RegExp("test");
二種方式都會建立出一個相同的正規表達式,並存放在pattern
這個變數上。
如果我們可以很確定正規表達式的內容,使用正規表達式實值比較好。
正規表達式是以詞?及運算子組合而成。詞?(term)是我們要搜尋的文字,比如說我們要找 abc
的話,那abc
就是詞?。運算子(operator)是正規表達式定義的一些符號,各代表某種規則,如同數學裡的+ - * %
符號,二者搭配使用可以建立各種規則。
相同的規則可以用不一樣的正規表達方式撰寫。
const pattern = /best/;
"the best" // 1 match
"the beast" // no match
字串要完全符合正規表達式中的詞?,例如正規表達式/best/
,在字串the best
可以匹配成功,在字串the beast
就無法匹配成功。
上面的方法跟使用String.includes()
沒什麼兩樣,正規表達式的能力當然不止如此。
有時我們有一些候選對象,只需要匹配其中一個就可以。正規表達式裡可以使用一對中括號[]
。
const pattern = /[abc]/;
"hello world" // no match
"hello world again" // 2 matches (2個a)
如果在中括號字?的左邊加上^
符號,表示要匹配除了字?以外的所有字元,不止英文字母,數字、符號、空格也會算進去。
const pattern = /[^abc]/;
"hello world" // 11 matches (10個字母加1個空格)
"hello world again" // 15 matches (13個字母加2個空格)
如果要匹配的字?是連續的資料,與其寫[12345]
,正規表達式可以用連接線將頭尾之間(包含頭尾)的字元納入這個集合裡[1-5]
。
const pattern = /[a-e]/;
"hello world" // 2 matches (e, d)
我們在寫字串時,如果剛好遇到字串中有單引號或雙引號,跟我們建立字串的符號一樣,我們會用反斜線\
在字串的符號前面,代表這是字串的一部份,不是字串的結尾,反斜線的功能就是讓符號「跳脫(escape)」設定的功能,回歸普通字串。
反斜線在正規表達式當中也具有同樣功能,如果我們的詞?裡有上面的[, ], -, ^
符號,就需要在前面加上反斜線讓它們變成匹配用的詞?。
如果詞?中有反斜線的話,就要連續用二條反斜線\\
來代表一個反斜線字元。
如果我們想確保字串的開頭符合某種模式,就在正規表達式的開頭加上^
符號,這裡沒有中括號,所以和上面「匹配字元集合」的地方是同一個符號不同功能。
const pattern = /^he/;
"hello world" // 1 match
"lorem ipsum" // no match
如果是需要結尾匹配的話,在正規表達式的最後加上$
符號。
const pattern = /sum$/;
"hello world" // no match
"lorem ipsum" // 1 match
如果同時使用^
和$
,表示字串要完全符合正規表達式。
const pattern = /^world$/
"hello world" // no match
"world" // 1 match
如果要尋找連續出現的同一個字元,正規表達式提供了一些規則讓我們能指定不同的重複條件。
在字元後面加上?
,表示該字元可以出現一次,或完全不出現。
const pattern = /t?est/;
"test" // 1 match
"est" // 1 match
"ttest" // no match
在字元後面加上+
,表示該字元應該至少出現一次。
const pattern = /t+est/;
"test" // 1 match
"est" // no match
"ttest" // 1 match
"tttest" // 1 match
在字元後面加上*
,表示該字元可以出現零次、一次,或多次。
const pattern = /t*est/;
"test" // 1 match
"est" // 1 match
"ttest" // 1 match
"tttest" // 1 match
字元後面加上一組大括號,裡面的數字代表字元指定重複的次數。
const pattern = /o{4}/;
"noob" // no match
"noooob" // 1 match
"noooooooob" // no match
在大括號裡面用逗號隔開二個數字,代表重複次數的範圍。
const pattern = /o{2,4}/;
"dog" // no match
"noob" // 1 match
"nooob" // 1 match
"noooob" // 1 match
"noooooooob" // no match
如果省略第二個數字,會變成開放式的範圍,也就是數字以上的重複次數都算匹配。
const pattern = /o{2,}/;
"dog" // no match
"noob" // 1 match
"noooob" // 1 match
"noooooooob" // 1 match
正規表達式在匹配上面這些重複符合有分以「貪婪(greedy)」或「不貪婪(nongreedy/lazy)」二種方式,預設會以 greedy 的方式匹配字串,也就是會回傳長度最長的匹配結果。如果在運算子後面加上?
符號,那麼就會以 lazy 的方式,回傳足夠符合正規表達式的匹配結果。
// 字串裡有 hel 三個字元,最後的 l 至少出現一次
const greedyPattern = /hel+/;
const lazyPattern = /hel+?/;
"hello"
// greedyPattern: "hell"
// lazyPattern: "hel"
上面範例當中,我們要找hel+
的正規表達式,表示hel
, hell
,helll
,以及不管最後的l
重複多少次都算匹配。而貪婪的匹配方式會回傳最長的結果,不貪婪的方式回傳足夠的結果,就產生了同樣”hello
字串卻得到"hell"
和"hel"
的差別。
有時我們想要匹配的字元是無法用文字表示,像是 tab 鍵和換行鍵這類。JavaScript 在正規表達式語法裡就為我們定義了一些詞?,讓我們能控制這些字元。這邊列出常見的:
\d
:十進位數字,等於[0-9]
\D
:十進位數字以外的其他字元,等於[^0-9]
\s
:任何空白(空格、tab 及換頁等等)\S
:空白字元之外的其他字元\w
:任何字母與數字字元,包括下底線,等於[A-Za-z0-9_]
\W
:字母、數字與下底線之外的其他字元,等於[^A-Za-z0-9_]
在數學運算式中我們會用括號()
表示裡面的表達式是自成一組,正規表達式也可以用同樣的方式。
// 匹配 pika, pikaa, pikaaa....
const pattern1 = /pika+/;
// 匹配 pika, pikapika, pikapikapika....
const pattern2 = /(pika)+/;
"pikapikachu"
// pattern1: 2 matches ("pika", "pika")
// pattern2: 1 match ("pikapika")
我們可以用|
符號來表示「或」這個運算。例如/a|b/
會符合a
或是b
字元,/(ab)|(cd)/
符合ab
或cd
字串。
最後來看一下正規表達式的五個 flags。這些 flags 是加在正規表達式斜線的後面。
i
—讓正規表達式不區分英文大小寫,所以不只test
會符合/test/i
,也包括Test
, TEST
和tEsT
等等。g
—匹配所有符合樣式的地方,和預設的區域匹配不同,區域匹配只會匹配到第一個符合樣式的地方。m
—允許對多行文字做匹配,例如對網頁上的多行文字區取值。y
—進行 sticky matching。一個對字串做 sticky matching 的正規表達式,只會在最後一次符合處之後進行匹配。u
—對 Unicode point 做跳脫。